package practice

import "fmt"

type person struct {
	name string
	age  int
}

//receiver是值类型的(这里的p person)，无论调用方是值类型的实例(p2)还是指针类型的实例(p1)，都是拷贝整个底层数据结构的.
//方法内部(这里的setname)访问的和修改的都是实例的副本。所以如果有修改操作，不会影响外部原始实例
func (p person) setname(name string) {
	fmt.Printf("setname中 p 的内存地址 = %p\n", &p)
	fmt.Printf("setname中 p 指向的内存地址 = %p\n", p) //引用类型则输出其值
	p.name = name                              //赋值的是拷贝的对象,不会影响调用方原始对象
}

//receiver是指针类型的(这里的p *person)，无论调用方是值类型的实例(p2)还是指针类型的实例(p1)，都是拷贝指针.
//方法内部(这里的getname)访问的和修改的都是原始的实例数据结构。所以如果有修改操作，会影响调用方原始实例
func (p *person) getname() string {
	return p.name
}
func (p *person) setage(age int) {
	p.age = age //会影响原始对象
}
func (p *person) getage() int {
	return p.age
}

func Main_valueReceiver_and_pointerReceiver() {
	/****************** 值类型/指针类型的receiver在方法调用上的差异 *******************/
	// 指针类型的实例
	p1 := new(person)
	p1.name = "yvon" //赋值有效
	fmt.Printf("p1 的内存地址 = %p\n", &p1)
	fmt.Printf("p1 指向的内存地址 = %p\n", p1)
	fmt.Println("=====================================")
	p1.setname("longshuai1") //所有对方法的调用都是值传递,即赋值p1传递到方法.此处p1是指针,赋值p1指向的值过去
	p1.setage(21)
	fmt.Println(p1.getname()) // 输出""
	fmt.Println(p1.getage())  // 输出21

	fmt.Println("***************************************************")

	// 值类型的实例
	p2 := person{}
	p2.name = "kaka" //赋值有效
	fmt.Printf("p2 的内存地址 = %p\n", &p2)
	fmt.Printf("p2 指向的内存地址 = %p\n", p2)
	fmt.Println("=====================================")
	p2.setname("longshuai2")
	p2.setage(23)
	fmt.Println(p2.getname()) // 输出""
	fmt.Println(p2.getage())  // 输出23
}

func Main_otherMethodType() {
	/****************** 方法还可以基于struct以外的类型 *******************/
	//方法的receiver type并非一定要是struct类型，还可以是: type定义的类型别名(如本例的int)、slice、map、channel、func等,interface不行
	n := new(myint)
	fmt.Println(n.numadd(4))
}

func Main_sliceAppend_which_changed_caller_pointer() {
	/****************** 1.切片append函数(会修改源变量指向地址)测试 *****************/
	var s = []string{"a", "b", "c"}
	fmt.Printf("切片初始, s 的内存地址 = %p\n", &s)  //0xc0000625a0
	fmt.Printf("切片初始, s 指向的内存地址 = %p\n", s) //0xc000074390
	fmt.Println("=====================================")

	//修改 slice 中的元素不会改变s本身的地址及其指向的地址
	s[0] = "aa"
	fmt.Printf("修改s[0] 之后, s 的内存地址 = %p\n", &s)  //0xc0000625a0
	fmt.Printf("修改s[0] 之后, s 指向的内存地址 = %p\n", s) //0xc000074390
	fmt.Println("=====================================")

	//append操作会改变s指向的地址
	s = append(s, "d")
	fmt.Printf("append之后, s 的内存地址 = %p\n", &s)  //0xc0000625a0
	fmt.Printf("append之后, s 指向的内存地址 = %p\n", s) //0xc0000a4000	已发生改变
	fmt.Println("=====================================")

	//len=4 cap=6 slice=[aa b c d]
	fmt.Printf("len=%d cap=%d slice=%v\n", len(s), cap(s), s)

	/****************** 2.函数传参(值传递)及对象地址变化测试 *******************/
	var ss = []string{"a", "b", "c"}
	fmt.Printf("切片初始, ss 的内存地址 = %p\n", &ss)  //0xc0000044c0		ss自身初始地址
	fmt.Printf("切片初始, ss 指向的内存地址 = %p\n", ss) //0xc000062330	ss初始指向地址
	fmt.Println("=================================================")

	//ss通过函数传参的方式,在AppendInFunc()内部修改及append添加
	AppendInFunc(ss)

	//AppendInFunc()中除了对切片的修改(ss[0])有效外,AppendInFunc()内ss自身地址及其指向地址的变化都不会反映到这里
	fmt.Printf("切片最终, ss 的内存地址 = %p\n", &ss)  //0xc0000044c0	与初始一致
	fmt.Printf("切片最终, ss 指向的内存地址 = %p\n", ss) //0xc000062330	与初始一致
	fmt.Println("=================================================")

	//仅ss[0] = "aa"修改生效
	fmt.Println(ss) //输出[aa b c]

}

func AppendInFunc(ss []string) {
	fmt.Printf("AppendInFunc收到, ss 的内存地址 = %p\n", &ss)  //0xc000004520	golang中函数参数是值传递,所以调用方ss自身地址复制到此已改变(但其指向地址不变)
	fmt.Printf("AppendInFunc收到, ss 指向的内存地址 = %p\n", ss) //0xc000062330	ss指向的地址不变
	fmt.Println("=================================================")
	ss[0] = "aa" //修改成功

	//这里append不会令外部原始变量受到影响:
	ss = append(ss, "e")
	fmt.Printf("doappend之后, ss 的内存地址 = %p\n", &ss)  //0xc000004520	ss自身地址不变,和传入时一致
	fmt.Printf("doappend之后, ss 指向的内存地址 = %p\n", ss) //0xc00003e060	指向地址发生改变: 因为append函数会使ss指向地址产生变化
	fmt.Println("=================================================")
}

type myint int

func (i *myint) numadd(n int) int {
	return n + 1
}
